package biz.datalk.industrialland.admin.config;


import biz.datalk.industrialland.common.component.redis.DatalkRedisKeyPrefixSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
 * redis 配置
 *
 * @author tarofang@163.com
 * @date 2019年08月11日
 */
@SuppressWarnings({"UnusedReturnValue", "unused"})
@Slf4j
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 自定义的缓存key的生成策略 若想使用这个key
     * 只需要讲注解上keyGenerator的值设置为keyGenerator即可
     *
     * @return 自定义策略生成的key
     *
     * @author tarofang@163.com
     * @date 2019年08月11日
     */
    @SuppressWarnings({"NullableProblems", "Convert2Lambda"})
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        log.debug("into");
        try {
            return new KeyGenerator() {
                @SuppressWarnings("StringBufferMayBeStringBuilder")
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    StringBuffer sb = new StringBuffer();
                    sb.append(target.getClass().getName());
                    sb.append(method.getName());
                    for (Object obj : params) {
                        sb.append(obj.toString());
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("generate: {}", sb.toString());
                    }
                    return sb.toString();
                }
            };
        } finally {
            log.debug("out");
        }
    }///~


    @Value("${datalk.redis.key.prefix:}")
    private String keyPrefix;

    @Bean("datalkRedisKeyPrefixSerializer")
    public RedisSerializer<String> datalkRedisKeyPrefixSerializer() {
        String tmpKeyPrefix = StringUtils.trimToEmpty(keyPrefix);
        return new DatalkRedisKeyPrefixSerializer(tmpKeyPrefix);
    }

    @Bean("datalkJackson2JsonRedisSerializer")
    public Jackson2JsonRedisSerializer<Object> datalkJackson2JsonRedisSerializer() {
        // Jackson 序列化
        Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jsonRedisSerializer.setObjectMapper(om);

        return jsonRedisSerializer;
    }

    /**
     * RedisTemplate配置
     */
    @SuppressWarnings("Convert2Diamond")
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            LettuceConnectionFactory lettuceConnectionFactory,//
            Jackson2JsonRedisSerializer<Object> datalkJackson2JsonRedisSerializer, //
            RedisSerializer<String> datalkRedisKeyPrefixSerializer) {
        log.debug("into");
        try {
            // 配置redisTemplate
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
            redisTemplate.setConnectionFactory(lettuceConnectionFactory);

            redisTemplate.setKeySerializer(datalkRedisKeyPrefixSerializer);
            redisTemplate.setHashKeySerializer(datalkRedisKeyPrefixSerializer);

            redisTemplate.setValueSerializer(datalkJackson2JsonRedisSerializer);
            redisTemplate.setHashValueSerializer(datalkJackson2JsonRedisSerializer);

            redisTemplate.setEnableTransactionSupport(false);

            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        } finally {
            log.debug("out");
        }
    }///~


    /**
     * 缓存配置管理器
     */
    @Bean
    public CacheManager cacheManager(
            LettuceConnectionFactory factory, //
            Jackson2JsonRedisSerializer<Object> datalkJackson2JsonRedisSerializer, //
            RedisSerializer<String> datalkRedisKeyPrefixSerializer) {
        log.debug("into");
        try {
            // 以锁写入的方式创建RedisCacheWriter对象
            RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
            // @formatter:off
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    // 设定缓存有效期为永久
                    // .entryTtl(Duration.ZERO)
                    // 设置缓存有效期 2 小时;
                    .entryTtl(Duration.ofHours(2))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(datalkRedisKeyPrefixSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(datalkJackson2JsonRedisSerializer))
                    ;
            // @formatter:on

            RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
            cacheManager.setTransactionAware(false);
            return cacheManager;
        } finally {
            log.debug("out");
        }
    }///~

}
